#include<sys/stat.h>
#include<sys/types.h>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <string>
#include <map>
#include <vector>
#include <iostream>
#include <iterator>
#include <functional>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/algorithm/string/find_iterator.hpp>
#include "print.hpp"
using namespace std;
using namespace boost;

void call_length_error(const string& idname, const string& fid, const string& genofile, const vector<int>& idlink_ids, const vector<string>& calls)
{
  cerr <<  "Error: The number of genotype calls corresponding to " << idname << " " << fid << " in genofile " << genofile << " should be the same as the number of idlink_ids (" << idlink_ids.size() << ") but is " << calls.size() << endl;
}

void write_geno_tables_affy6_to_file(const string& tempdirname, int discard_lines, int sync_interval, const string& genofile, const vector<int>& idlink_ids, const map<string, int>& annodict)
{
  vector<string> idlink_ids_str;
  idlink_ids_str.reserve(idlink_ids.size());
  for(size_t i=0; i<idlink_ids.size(); i++)
    idlink_ids_str.push_back(lexical_cast<string>(idlink_ids[i]));
  for(size_t i=0; i<idlink_ids.size(); i++)
    {
      std::stringstream genotablefile;
      genotablefile << tempdirname << "/geno" << idlink_ids[i];
      std::remove(genotablefile.str().c_str());
    }

  if(mkdir(tempdirname.c_str(), 0755)==-1)//creating a directory
   {
     cout << tempdirname << ": " << strerror(errno) << endl;
   }
  size_t idlen = idlink_ids.size();
  ifstream gf(genofile.c_str());
  string gstr, line;
  for(int i=0; i<discard_lines; i++)
    getline(gf, line);
  int linepos=0, linesyncpos=0;
  vector<string> glist;
  while(getline(gf, line))
  {
    trim(line);
    vector<string> line_split;
    split(line_split, line, is_any_of("\t"));
    string fid = line_split[0];
    vector<string> calls(line_split.begin()+1, line_split.end());
    if(calls.size() != idlen)
      {
	call_length_error("fid", fid, genofile, idlink_ids, calls);
	exit(1);
      }
    if(annodict.find(fid) != annodict.end())
      {
	linepos++;
	string anno_id = lexical_cast<string>(annodict.find(fid)->second);
	for(size_t i=0; i<idlink_ids.size(); i++)
	  {
	    string idlink_id = idlink_ids_str[i];
	    string call = calls[i];
            string val;
            val += idlink_id;
            val += ",";
            val += anno_id;
            val += ",";
            val += call;
            val += "\n";
	    glist.push_back(val);
	  }
      }
//     if(linepos%1000 == 0)
//       cout << "linepos is " << linepos << endl;
    if(linesyncpos < linepos && linepos%sync_interval == 0)
      {
	linesyncpos = linepos;
	for(size_t j=0; j<idlink_ids.size(); j++)
	  {
	    int idlink_id = idlink_ids[j];
	    std::stringstream genotablefile;
	    genotablefile << tempdirname << "/geno" << idlink_id;
	    ofstream gtf(genotablefile.str().c_str(), std::ios::out | std::ios::app);
	    if(!gtf)
	      {
		cerr << "cannot open file "<< genotablefile.str() << endl;
	      }
	    gstr = "";
	    for(size_t i=j; i<glist.size(); i=i+idlen)
	      {
		gstr += glist[i];
	      }
	    gtf << gstr;
	    gtf.close();
	  }
	glist = vector<string>();
      }
  }
  for(size_t j=0; j<idlink_ids.size(); j++)
    {
      int idlink_id = idlink_ids[j];
      std::stringstream genotablefile;
      genotablefile << tempdirname << "/geno" << idlink_id;
      ofstream gtf(genotablefile.str().c_str(), std::ios::out | std::ios::app);
      if(!gtf)
	{
	  cerr << "cannot open file "<< genotablefile.str() << endl;
	}
      vector<string> glisttmp;
      gstr = "";
      for(size_t i=j; i<glist.size(); i=i+idlen)
	gstr += glist[i];
      gtf << gstr;
      gtf.close();
    }
  gf.close();
}

class encode_genotype
{
public:
  string aa;
  string ab;
  encode_genotype(){}
  encode_genotype(const string& _allelea, const string& _alleleb):aa(_allelea), ab(_alleleb){}
  string operator()(const string& call)
  {
    string a(call.substr(0,1));
    string b(call.substr(1));
    if(a == "-" && b == "-")
      return "-1";
    else if(a == aa && b == aa)
      return "0";
    else if(a == aa && b == ab)
      return "1";
    else if(a == ab && b == ab)
      return "2";
    // Return 100 as corresponding to runtime error
    else
      return "100";
    // else
    //   {
    // 	std::stringstream buffer;
    // 	//raise RuntimeError, "error in encode_genotype, call is %s, allelea is %s, alleleb is %s"%(call, self.allelea, self.alleleb);
    // 	buffer << "error in encode_genotype, call is " << call << ", allelea is " << allelea << ", alleleb is " << alleleb << endl;
    // 	throw std::runtime_error(buffer.str());
    //   }
  }
};

inline void add_illumina_calls_to_glist(map<string, map<string, string> >& annodict, vector<string>&  glist, int& linepos, const string& rsid, const string& alleletype, const vector<string>& idlink_ids_str, const vector<string>& calls, set<string>& error_rsids)
{
  size_t idlen = idlink_ids_str.size();
  linepos++;
  map<string, string> annovals(annodict[rsid]);
  string anno_id = annovals["id"];
  string allelea_forward(annovals["allelea_forward"]), alleleb_forward(annovals["alleleb_forward"]),
    allelea_top(annovals["allelea_top"]), alleleb_top(annovals["alleleb_top"]);
  encode_genotype eg;
  if(alleletype == "forward")
    eg = encode_genotype(allelea_forward, alleleb_forward);
  else if(alleletype == "top")
    encode_genotype eg = encode_genotype(allelea_top, alleleb_top);
  else
    {
      std::stringstream buffer;
      buffer << "illegal value of alleletype %s " << alleletype << endl;
      throw std::runtime_error(buffer.str());
    }
  for(size_t i=0; i<idlen; i++)
    {
      string idlink_id = idlink_ids_str[i];
      string call = calls[i];
      // encode_genotype returns error
      if(eg(call) != "100")
	{
	  string val;
	  val += idlink_id;
	  val += ",";
	  val += anno_id;
	  val += ",";
	  val += eg(call);
	  val += "\n";
	  glist.push_back(val);
	}
      else
	{
	  cout << "problems detected with call " << call << " in " << rsid << endl;
	  error_rsids.insert(rsid);
	  // remove all occurrences of rsid from glist.
	  glist.erase(glist.end()-i, glist.end());
	  break;
	}
    }
}

void write_geno_tables_illumina_to_file(const string& tempdirname, int discard_lines, int sync_interval, const string& genofile, const vector<int>& idlink_ids, map<string, map<string, string> > annodict, const string& alleletype)
{
  vector<string> idlink_ids_str;
  idlink_ids_str.reserve(idlink_ids.size());
  for(size_t i=0; i<idlink_ids.size(); i++)
    idlink_ids_str.push_back(lexical_cast<string>(idlink_ids[i]));
  for(size_t i=0; i<idlink_ids.size(); i++)
    {
      std::stringstream genotablefile;
      genotablefile << tempdirname << "/geno" << idlink_ids[i];
      std::remove(genotablefile.str().c_str());
    }
  if(mkdir(tempdirname.c_str(), 0755)==-1)//creating a directory
    {
      cerr << tempdirname << ": " << strerror(errno) << endl;
    }
  size_t idlen = idlink_ids.size();
  ifstream gf(genofile.c_str());
  string gstr, line;
  for(int i=0; i<discard_lines; i++)
    getline(gf, line);
  int linepos=0, linesyncpos=0;
  vector<string> glist;
  set<string> error_rsids;

  while(getline(gf, line))
    {
      trim(line);
      vector<string> line_split;
      split(line_split, line, is_any_of("\t"));
      string rsid = line_split[0];
      vector<string> calls(line_split.begin()+1, line_split.end());
      if(calls.size() != idlen)
	{
	  call_length_error("rsid", rsid, genofile, idlink_ids, calls);
	  exit(1);
	}
      if(annodict.find(rsid) != annodict.end())
	add_illumina_calls_to_glist(annodict, glist, linepos, rsid, alleletype, idlink_ids_str, calls, error_rsids);
      // if(linepos%1000 == 0)
      // cout << "linepos is " << linepos << endl;
      if(linesyncpos < linepos && linepos%sync_interval == 0)
	{
	  linesyncpos = linepos;
	  for(size_t j=0; j<idlink_ids.size(); j++)
	    {
	      int idlink_id = idlink_ids[j];
	      std::stringstream genotablefile;
	      genotablefile << tempdirname << "/geno" << idlink_id;
	      ofstream gtf(genotablefile.str().c_str(), std::ios::out | std::ios::app);
	      if(!gtf)
		{
		  cerr << "cannot open file "<< genotablefile.str() << endl;
		}
	      gstr = "";
	      for(size_t i=j; i<glist.size(); i=i+idlen)
		{
		  gstr += glist[i];
		}
	      gtf << gstr;
	      gtf.close();
	    }
	  glist = vector<string>();
	}
    }
  for(size_t j=0; j<idlink_ids.size(); j++)
    {
      int idlink_id = idlink_ids[j];
      std::stringstream genotablefile;
      genotablefile << tempdirname << "/geno" << idlink_id;
      ofstream gtf(genotablefile.str().c_str(), std::ios::out | std::ios::app);
      if(!gtf)
	{
	  cerr << "cannot open file "<< genotablefile.str() << endl;
	}
      vector<string> glisttmp;
      gstr = "";
      for(size_t i=j; i<glist.size(); i=i+idlen)
	gstr += glist[i];
      gtf << gstr;
      gtf.close();
    }
  gf.close();
  cout << "There were problems converting the genotype calls of the following \
rsids to integers, so all the calls corresponding to those rsids were \
discarded: " << error_rsids << endl;
}

void write_geno_table_affy6_to_file(int discard_lines, int discard_cols, int sync_interval, const string& genofile, const string& genotablefile, const vector<int>& idlink_ids, const map<string, int>& annodict)
{
  size_t idlen = idlink_ids.size();
  vector<string> idlink_ids_str;
  idlink_ids_str.reserve(idlink_ids.size());
  for(size_t i=0; i<idlink_ids.size(); i++)
    idlink_ids_str.push_back(lexical_cast<string>(idlink_ids[i]));
  std::remove(genotablefile.c_str());
  ofstream gtf(genotablefile.c_str(), std::ios::out | std::ios::app);
  if(!gtf)
    {
      cerr << "cannot open file " << genotablefile << endl;
    }
  ifstream gf(genofile.c_str());
  string gstr, line;
  for(int i=0; i<discard_lines; i++)
    getline(gf, line);
  int linepos=0, linesyncpos=0;
  while(getline(gf, line))
  {
    trim(line);
    vector<string> line_split;
    split(line_split, line, is_any_of("\t"));
    string fid = line_split[0];
    vector<string> calls(line_split.begin()+1, line_split.end());
    if(calls.size() != idlen)
      {
	call_length_error("fid", fid, genofile, idlink_ids, calls);
	exit(1);
      }
    if(annodict.find(fid) != annodict.end())
      {
	linepos++;
	string anno_id = lexical_cast<string>(annodict.find(fid)->second);
	for(size_t i=0; i<idlink_ids.size(); i++)
	  {
	    string idlink_id = idlink_ids_str[i];
	    string call = calls[i];
            gstr += idlink_id;
            gstr += ",";
            gstr += anno_id;
            gstr += ",";
            gstr += call;
            gstr += "\n";
	  }
      }
//     if(linepos%1000 == 0)
//       cout << "linepos is " << linepos << endl;
    if(linesyncpos < linepos && linepos%sync_interval == 0)
      {
	linesyncpos = linepos;
	gtf << gstr;
	gstr = "";
      }
  }
  gtf << gstr;
  gtf.close();
  gf.close();
}

void write_geno_table_illumina_to_file(int discard_lines, int sync_interval, const string& genofile, const string& genotablefile, const vector<int>& idlink_ids, map<string, map<string, string> > annodict, const string& alleletype)
{
  vector<string> idlink_ids_str;
  idlink_ids_str.reserve(idlink_ids.size());
  for(size_t i=0; i<idlink_ids.size(); i++)
    idlink_ids_str.push_back(lexical_cast<string>(idlink_ids[i]));
  std::remove(genotablefile.c_str());
  ofstream gtf(genotablefile.c_str(), std::ios::out | std::ios::app);
  if(!gtf)
    {
      cerr << "cannot open file "<< genotablefile << endl;
    }
  size_t idlen = idlink_ids.size();
  ifstream gf(genofile.c_str());
  string gstr, line;
  for(int i=0; i<discard_lines; i++)
    getline(gf, line);
  int linepos=0, linesyncpos=0;
  vector<string> glist;
  set<string> error_rsids;

  while(getline(gf, line))
    {
      trim(line);
      vector<string> line_split;
      split(line_split, line, is_any_of("\t"));
      string rsid = line_split[0];
      vector<string> calls(line_split.begin()+1, line_split.end());
      if(calls.size() != idlen)
	{
	  call_length_error("rsid", rsid, genofile, idlink_ids, calls);
	  exit(1);
	}
      if(annodict.find(rsid) != annodict.end())
	add_illumina_calls_to_glist(annodict, glist, linepos, rsid, alleletype, idlink_ids_str, calls, error_rsids);

      // if(linepos%1000 == 0)
      //   cout << "linepos is " << linepos << endl;
      if(linesyncpos < linepos && linepos%sync_interval == 0)
	{
	  linesyncpos = linepos;
	  ofstream gtf(genotablefile.c_str(), std::ios::out | std::ios::app);
	  gstr = "";
	  for(size_t i=0; i<glist.size(); ++i)
	    {
	      gstr += glist[i];
	    }
	  gtf << gstr;
	  gtf.close();
	  glist = vector<string>();
	}
    }
  gstr = "";
  for(size_t i=0; i<glist.size(); ++i)
    gstr += glist[i];
  gtf << gstr;
  gtf.close();
  gf.close();
  cout << "There were problems converting the genotype calls of the following \
rsids to integers, so all the calls corresponding to those rsids were \
discarded: " << error_rsids << endl;
}

void truncate_genofile(const string& genofile, const string& truncgenofile, const vector<string>& idlist, const int discard_lines)
{
  ifstream gf(genofile.c_str());
  ofstream tgf(truncgenofile.c_str());
  string line;
  for(int i=0; i<discard_lines; i++)
    {
      getline(gf, line);
      tgf << line << endl;
    }
  int linenum = 0;
  string id;
  while(getline(gf, line))
    {
      linenum += 1;
      trim(line);
      vector<string> line_split;
      split(line_split, line, is_any_of("\t"));
      id = line_split[0];
      if(find(idlist.begin(), idlist.end(), id) != idlist.end())
	{
	  tgf << line << endl;
	}
    }
}
